﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace AssemblyVersioner
{
	internal class Program
	{
		private static void Main(string[] args)
		{
			if (args.Length != 1)
			{
				Console.WriteLine("Invalid usage. Provide *.eav file path.");
				Console.ReadLine();
				return;
			}

			// Instantiate 
			AppInstance inst = new AppInstance(args[0]);
		}

		internal class AppInstance
		{
			#region Fields

			private const string Build_String = "Build";
			private const string Major_String = "Major";
			private const string Minor_String = "Minor";
			private const string Ask_String = "Ask";
			private const string Revision_String = "Revision";
			private static readonly string AssemblyInfo_String = "AssemblyInfo.cs";
			private static readonly string Configuration_String = "Configuration";
			private static readonly string CurrentVersion_String = "CurrentVersion";
			private static readonly string SetNext_String = "SetNext";
			private static readonly string Value_String = "Value";
			private static readonly string WorkFolder_String = "WorkFolder";
			private XDocument Config;
			private XElement CurrentVersion;
			private String Destination;
			private String FilePath;
			private String NextVersionNumber;
			private XElement SetNext;
			private XElement WorkFolder;
			private XElement Section;

			#endregion Fields

			#region Constructors

			public AppInstance(String iFilePath)
			{
				try
				{
					FilePath = iFilePath;

					// Get Destination and config 
					SetDestination();

					// Check if path exists 
					CheckDestination();

					// Check if autoincrement version 
					SetAutoincrement();

					// Set version to files 
					SetVersion();

					// Exit 
					Exit();
				}
				catch (Exception e)
				{
					Console.ForegroundColor = ConsoleColor.Red;
					Console.WriteLine(String.Format("Error: {0}. StackTrace: {1}", e.Message, e.StackTrace));
					Console.ReadLine();
#if DEBUG
					throw;
#endif
				}
			}

			#endregion Constructors

			#region Private Methods

			private void CheckDestination()
			{
				// Check if path exists 
				if (Directory.Exists(Destination))
				{
					Console.ForegroundColor = ConsoleColor.Green;
					DirectoryInfo di = new DirectoryInfo(Destination);
					Destination = di.FullName;
					Console.WriteLine(string.Format("Working target: {0}", Destination));
					Console.WriteLine();
				}
				else
				{
					throw new Exception(String.Format("Folder not found: {0}.", Destination));
				}
			}

			private void Exit()
			{
				// Exit 
				Console.WriteLine();
				Console.WriteLine("Press Enter to exit.");
				Console.ReadLine();
			}

			private void ModifyFile(string iNext, string iPath, String iContent)
			{
				// Get from the AssemblyInfo.cs file, the row to update by looking for the string 
				var RegExpMatch = Regex.Match(iContent, @"\[assembly:\sAssemblyVersion\(\""\d+\.\d+\.\d+\.\d+\""\)\]");
				while (RegExpMatch.Success)
				{
					int StartIndex = RegExpMatch.Index + 28;
					int LastIndex = RegExpMatch.Index + RegExpMatch.Length - 3;
					iContent = iContent.Remove(StartIndex, LastIndex - StartIndex);
					iContent = iContent.Insert(StartIndex, iNext);
					RegExpMatch = RegExpMatch.NextMatch();
				}

				RegExpMatch = Regex.Match(iContent, @"\[assembly:\sAssemblyFileVersion\(\""\d+\.\d+\.\d+\.\d+\""\)\]");
				while (RegExpMatch.Success)
				{
					int StartIndex = RegExpMatch.Index + 32;
					int LastIndex = RegExpMatch.Index + RegExpMatch.Length - 3;
					iContent = iContent.Remove(StartIndex, LastIndex - StartIndex);
					iContent = iContent.Insert(StartIndex, iNext);
					RegExpMatch = RegExpMatch.NextMatch();
				}

				// Write the AssemblyInfo.cs file 
				File.WriteAllText(iPath, iContent);
			}

			// Writes the new version in all the assemblies info files 
			private void SetAssemblyInfoVersion(List<String> iPaths, String iNext)
			{
				foreach (string path in iPaths)
				{
					// Change readonly attibutes 
					FileInfo fi = new FileInfo(path);
					if ((fi.Attributes & System.IO.FileAttributes.ReadOnly) != 0)
					{
						fi.Attributes -= System.IO.FileAttributes.ReadOnly;
					}

					// Read the assembly info file in a local string variable 
					String content = File.ReadAllText(fi.FullName);

					ModifyFile(iNext, path, content);
				}
			}

			private void SetAutoincrement()
			{
				// Check if autoincrement version 
				String SetNextValue = SetNext.Attribute(Value_String).Value;
				if (SetNextValue != Ask_String)
				{
					// Get current version from config 
					String CurrentVersionValue = CurrentVersion.Attribute(Value_String).Value;
					Version v = new Version(CurrentVersionValue);
					switch (SetNextValue)
					{
						case Major_String:
							v = new Version(v.Major + 1, v.Minor, v.Build, v.Revision);
							break;

						case Minor_String:
							v = new Version(v.Major, v.Minor + 1, v.Build, v.Revision);
							break;

						case Build_String:
							v = new Version(v.Major, v.Minor, v.Build + 1, v.Revision);
							break;

						case Revision_String:
							v = new Version(v.Major, v.Minor, v.Build, v.Revision + 1);
							break;

						default:
							throw new Exception("Invalid SetNext value");
					}

					NextVersionNumber = v.ToString(4);

					Console.Write(String.Format("The next version number is: {0}", NextVersionNumber));
					Console.WriteLine();
					Console.WriteLine();
				}
				else
				{
					// Read new version from console 
					Console.Write("Input the next version number: ");
					NextVersionNumber = Console.ReadLine();
					Console.WriteLine();
				}
			}

			private void SetDestination()
			{
				// Get launch folder 
				String ConfigFolder = FilePath.Remove(FilePath.LastIndexOf('\\') + 1);

				// Get Config 
				Config = XDocument.Load(FilePath);

				// Get relative root folder path 
				Section = Config.Element(Configuration_String);
				EnsureElement(Section, Configuration_String);

				CurrentVersion = Section.Element(CurrentVersion_String);
				EnsureElement(CurrentVersion, CurrentVersion_String);

				SetNext = Section.Element(SetNext_String);
				EnsureElement(SetNext, SetNext_String);

				WorkFolder = Section.Element(WorkFolder_String);
				EnsureElement(WorkFolder, WorkFolder_String);

				string PathFromConfig = WorkFolder.Attribute(Value_String).Value;

				// Folder to process 
				Destination = Path.Combine(ConfigFolder, PathFromConfig);
			}

			private void SetVersion()
			{
				// Set version to files 
				String Error;
				if (WorkOutFile(out Error))
				{
					// Save new version to config 
					CurrentVersion.Attribute(Value_String).Value = NextVersionNumber;
					Config.Save(FilePath);

					// Done 
					Console.WriteLine("Done");
				}
				else
				{
					// Failed 
					Console.ForegroundColor = ConsoleColor.Red;
					Console.WriteLine(String.Format("Failed. Error: {0}.", Error));
				}
			}

			private bool WorkOutFile(out string error)
			{
				try
				{
					error = string.Empty;
					// Getting all AssemblyInfo.cs files 
					Console.WriteLine("Getting the AssemblyInfo.cs files");
					Console.WriteLine();
					String[] FilePaths = Directory.GetFiles(Destination, AssemblyInfo_String, SearchOption.AllDirectories);
					List<String> AssemblyInfoPaths = new List<string>();
					AssemblyInfoPaths.AddRange(FilePaths);

					// Set the assemblyInfo with the new version: 
					Console.WriteLine("Setting the specified version to all AssemblyInfo.cs files");
					Console.WriteLine();
					SetAssemblyInfoVersion(AssemblyInfoPaths, NextVersionNumber);
					return true;
				}
				catch (Exception e)
				{
					error = e.Message;
					return false;
				}
			}

			private void WriteError(String iError)
			{
				Console.ForegroundColor = ConsoleColor.Red;
				Console.WriteLine(iError);
				Console.ReadLine();
			}

			private static void EnsureElement(XElement item, String name)
			{
				if (item == null)
				{
					throw new Exception(String.Format("Element not found: {0}.", name));
				}
			}

			#endregion Private Methods
		}
	}
}